#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <wingdi.h>
#include <process.h>
#include <OleCtl.h>
#include <OCIdl.h>
#include <Objidl.h>
#include "ddraw.h"
#include "d2d.h"

static HINSTANCE g_ddrawdll = NULL;
static LPDIRECTDRAW g_ddraw = NULL;
static LPDIRECTDRAWSURFACE g_primary = NULL;
static LPDIRECTDRAWSURFACE g_back = NULL;
static LPDIRECTDRAWCLIPPER g_clipper = NULL;
static LPDIRECTDRAWCLIPPER g_backclipper = NULL;

static int g_iswin = TRUE; /* if window mode */
static HWND g_hwnd = NULL;
static int g_mousex, g_mousey;
static int g_active = TRUE;
/* mouse cursor handle */
static HCURSOR g_hcur = NULL;
/* show mouse cursor? */
static int g_showcur = TRUE;

/* font */
DWORD g_textcolor = 0;
int g_x = 0;
int g_y = 0;

/* time */

static D2D_LOOPFUNC g_loopfunc = NULL;
static double g_advicefps = 60.0; /* 建议值 */
static double g_fps = 0;

/*  Make the class name into a global variable  */
static wchar_t szClassName[ ] = L"D2D";

/*
  dll interfaces
*/

/* ddraw.dll */
typedef HRESULT WINAPI (*T_DirectDrawCreate)( GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter );

/* ole32.dll */
typedef HRESULT __stdcall (*T_CreateStreamOnHGlobal)(HGLOBAL,BOOL,LPSTREAM*);
T_CreateStreamOnHGlobal g_CreateStreamOnHGlobal = NULL;
typedef HRESULT __stdcall (*T_OleLoadPicture)(LPSTREAM,LONG,BOOL,REFIID,PVOID*);
T_OleLoadPicture g_OleLoadPicture = NULL;

/* winmm.dll */
typedef BOOL WINAPI (*T_sndPlaySoundA)(LPCSTR,UINT);
typedef MMRESULT WINAPI (*T_waveOutGetVolume)(HWAVEOUT,PDWORD);
typedef MMRESULT WINAPI (*T_waveOutSetVolume)(HWAVEOUT,DWORD);
typedef BOOL WINAPI (*T_PlaySoundW)(LPCWSTR,HMODULE,DWORD);
typedef DWORD WINAPI (*T_timeGetTime)(void);

T_sndPlaySoundA g_sndPlaySoundA = NULL;
T_waveOutGetVolume g_waveOutGetVolume = NULL;
T_waveOutSetVolume g_waveOutSetVolume = NULL;
T_PlaySoundW g_PlaySoundW = NULL;
T_timeGetTime g_timeGetTime = NULL;


/* gdi32.dll */

typedef WINGDIAPI BOOL WINAPI (*T_DeleteObject)(HGDIOBJ);
T_DeleteObject g_DeleteObject = NULL;
typedef WINGDIAPI HFONT WINAPI (*T_CreateFontW)(int,int,int,int,int,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,LPCWSTR);
T_CreateFontW g_CreateFontW = NULL;
typedef WINGDIAPI int WINAPI (*T_GetDeviceCaps)(HDC,int);
T_GetDeviceCaps g_GetDeviceCaps = NULL;
typedef WINGDIAPI int WINAPI (*T_SetBkMode)(HDC,int);
T_SetBkMode g_SetBkMode = NULL;
typedef WINGDIAPI HGDIOBJ WINAPI (*T_SelectObject)(HDC,HGDIOBJ);
T_SelectObject g_SelectObject = NULL;
typedef WINGDIAPI BOOL WINAPI (*T_TextOutW)(HDC,int,int,LPCWSTR,int);
T_TextOutW g_TextOutW = NULL;
typedef WINGDIAPI COLORREF WINAPI (*T_SetTextColor)(HDC,COLORREF);
T_SetTextColor g_SetTextColor = NULL;

static void initdll_winmm()
{
    static BOOL load = FALSE;
    if (load) return;
    load = TRUE;

    HINSTANCE hi = LoadLibraryW(L"winmm.dll");
    if (!hi) {
        d2d_msgbox("cant find winmm.dll");
        exit(0);
    }
    g_sndPlaySoundA = (T_sndPlaySoundA)GetProcAddress(hi, "sndPlaySoundA");
    g_waveOutGetVolume = (T_waveOutGetVolume)GetProcAddress(hi, "waveOutGetVolume");
    g_waveOutSetVolume = (T_waveOutSetVolume)GetProcAddress(hi, "waveOutSetVolume");
    g_PlaySoundW = (T_PlaySoundW)GetProcAddress(hi, "PlaySoundW");
    g_timeGetTime = (T_timeGetTime)GetProcAddress(hi, "timeGetTime");
}

static void initdll_gdi32()
{
    static BOOL load = FALSE;
    if (load) return;
    load = TRUE;

    HINSTANCE hi = LoadLibraryW(L"gdi32.dll");
    if (!hi) {
        d2d_msgbox("cant find gdi32.dll");
        exit(0);
    }
    g_DeleteObject = (T_DeleteObject)GetProcAddress(hi, "DeleteObject");
    g_CreateFontW = (T_CreateFontW)GetProcAddress(hi, "CreateFontW");
    g_GetDeviceCaps = (T_GetDeviceCaps)GetProcAddress(hi, "GetDeviceCaps");
    g_SetBkMode = (T_SetBkMode)GetProcAddress(hi, "SetBkMode");
    g_SelectObject = (T_SelectObject)GetProcAddress(hi, "SelectObject");
    g_TextOutW = (T_TextOutW)GetProcAddress(hi, "TextOutW");
    g_SetTextColor = (T_SetTextColor)GetProcAddress(hi, "SetTextColor");
}

static void initdll_ole32()
{
    static BOOL load = FALSE;
    if (load) return;
    load = TRUE;

    HINSTANCE hi = LoadLibraryW(L"ole32.dll");
    if (!hi) {
        d2d_msgbox("cant find ole32.dll");
        exit(0);
    }
    g_CreateStreamOnHGlobal = (T_CreateStreamOnHGlobal)GetProcAddress(hi, "CreateStreamOnHGlobal");

    hi = LoadLibraryW(L"oleaut32.dll");
    if (!hi) {
        d2d_msgbox("cant find oleaut32.dll");
        exit(0);
    }
    g_OleLoadPicture = (T_OleLoadPicture)GetProcAddress(hi, "OleLoadPicture");
}

static wchar_t* UTF8toUnicode(const char* s8)
{
    if (!s8) return NULL;
    DWORD ulen = MultiByteToWideChar (CP_UTF8, 0, s8, -1, NULL, 0);
    wchar_t* us = (wchar_t*)malloc(ulen*sizeof(wchar_t));
    memset(us, 0, ulen*sizeof(wchar_t));
    MultiByteToWideChar (CP_UTF8, 0, s8, -1, us, ulen);
    return us;
}

extern void d2d_msgbox (const char *info)
{
    wchar_t* us = UTF8toUnicode(info);
    MessageBoxW (g_hwnd, us, L"D2D info", MB_OK);
    free(us);
}

extern void* d2d_loadrc(const char *rctype, const char* rcname, int *rclen)
{
    wchar_t* wrctype = UTF8toUnicode(rctype);
    wchar_t* wrcname = UTF8toUnicode(rcname);
    HRSRC hres = FindResourceW(NULL, wrcname, wrctype);
    if (!hres) {
        free(wrctype);
        free(wrcname);
        return NULL;
    }
    HGLOBAL hrc = LoadResource(NULL, hres);
    *rclen = SizeofResource(NULL, hres);
    return LockResource(hrc);
}

/* show mouse curor */
extern void d2d_showmouse (BOOL yes)
{
    if (yes)
        g_showcur = TRUE;
    else {
        g_showcur = FALSE;
        SetCursor(NULL);
    }
}

/* get message in the game loop, return the message id or 0 */
extern int peekmsg (MSG *msg)
{
    MSG tmp;

    if (msg == NULL)
        msg = &tmp;

    if (PeekMessageW (msg, 0, 0, 0, PM_REMOVE)) {
        /* Translate virtual-key messages into character messages */
        // TranslateMessage(msg);
        /* Send message to WindowProcedure */
        DispatchMessageW(msg);

        return msg->message;
    }

    return 0;
}

/*  This function is called by the Windows function DispatchMessage()  */
static LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            return 0;
        case WM_ACTIVATE:
            g_active = (LOWORD(wParam) != WA_INACTIVE);
            break;
        case WM_SIZE:
            //resize (hwnd);
            break;
        case WM_MOUSEMOVE:
            g_mousex = LOWORD(lParam);
            g_mousey = HIWORD(lParam);
            break;
		case WM_SETCURSOR:
			if(g_showcur == FALSE && d2d_mousein()) SetCursor(NULL);
			else if (g_hcur == NULL) SetCursor(LoadCursor(NULL, IDC_ARROW));
			else SetCursor (g_hcur);
			return 0;
    }

    return DefWindowProcW (hwnd, message, wParam, lParam);
}

/* create a frame window and return the handle */
extern HWND winopen (const char *title, int width, int height, const char *icorc)
{
    HWND hwnd;               /* This is the handle for our window */
    WNDCLASSEXW wincl;        /* Data structure for the windowclass */
    HINSTANCE this_instance = GetModuleHandle (NULL);
    RECT rectW;
    wchar_t* wtitle = NULL;
    wchar_t* wicorc = NULL;

    /* The Window structure */
    wincl.hInstance = this_instance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEXW);

    /* Use default icon and mouse-pointer */
    //wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    if (icorc)
        wicorc = UTF8toUnicode(icorc);
    wincl.hIcon = LoadIconW (this_instance, wicorc);
    wincl.hIconSm = LoadIconW (this_instance, wicorc);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassExW (&wincl))
        return 0;

    //DWORD style = (WS_DLGFRAME | WS_SYSMENU) & (~WS_MAXIMIZEBOX);
    DWORD style = (WS_OVERLAPPEDWINDOW | WS_SYSMENU) & (~WS_MAXIMIZEBOX) & (~WS_THICKFRAME);

    /* get real window rect by our game's width and height. */
	rectW.left = (GetSystemMetrics(SM_CXSCREEN)-width)/3;
	rectW.top = (GetSystemMetrics(SM_CYSCREEN)-height)/2.2;
	rectW.right = rectW.left + width;
	rectW.bottom = rectW.top + height;
    AdjustWindowRect(&rectW, style, FALSE);

    wtitle = UTF8toUnicode(title);

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowExW (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           wtitle,       /* Title Text */
           style, /* default window */
           rectW.left,       /* Windows decides the position */
           rectW.top,       /* where the window ends up on the screen */
           rectW.right-rectW.left,                 /* The programs width */
           rectW.bottom-rectW.top,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           this_instance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, SW_SHOW);

    free(wicorc);
    free(wtitle);

    return hwnd;
}

extern void d2d_set(const char* p, ...)
{
    va_list ap;

    va_start (ap, p);

    if (strcasecmp(p, "title") == 0) {
        char* title = va_arg(ap, char*);
        wchar_t* wtitle = UTF8toUnicode(title);
        SetWindowTextW(g_hwnd, wtitle);
        free(wtitle);
    }
    else
    if (strcasecmp(p, "icon") == 0) {
        static HICON hicon = NULL;
        char* iconname = va_arg(ap, char*);
        BOOL isrc = FALSE;
        if (iconname[0] == ':') {
            iconname = iconname + 1;
            isrc = TRUE;
        }
        wchar_t* wiconname = UTF8toUnicode(iconname);
        if (hicon) DestroyIcon(hicon);
        if (isrc)
            hicon = LoadIconW (GetModuleHandle(NULL), wiconname);
        else
            hicon = LoadImageW(NULL, wiconname, IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
        free(wiconname);
        SendMessage(g_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon);
    }
    else
    if (strcasecmp(p, "cursor") == 0) {
        char* curname = va_arg(ap, char*);
        BOOL isrc = FALSE;
        if (curname[0] == ':') {
            curname = curname + 1;
            isrc = TRUE;
        }
        wchar_t* wcurname = UTF8toUnicode(curname);
        if (g_hcur) DestroyCursor(g_hcur);
        if (isrc)
            g_hcur = LoadCursorW(GetModuleHandle(NULL), wcurname);
        else
            g_hcur = LoadCursorFromFileW (wcurname);
        free(wcurname);
    }
    else
    if (strcasecmp(p, "fps") == 0) {
        int fps = va_arg(ap, int);
        g_advicefps = (double)fps;
    }

    va_end(ap);
}

extern double d2d_clock ()
{
    double now;

    //now = GetTickCount () / 1000.0;
    now = g_timeGetTime() / 1000.0;

    return now;
}

extern BOOL d2d_mousein()
{
    POINT p;
    RECT crect;

    GetCursorPos(&p);
    ScreenToClient(g_hwnd, &p);
    GetClientRect(g_hwnd, &crect);
    return PtInRect(&crect, p);
}

extern BOOL d2d_iskeydown (int kid)
{
    if (g_active && !((kid==K_LBUTTON || kid==K_RBUTTON) && !d2d_mousein()) )
        return ((GetAsyncKeyState(kid) & 0x8000) != 0);

    return FALSE;
}

extern BOOL d2d_iskeyup (int kid)
{
    if (g_active && !((kid==K_LBUTTON || kid==K_RBUTTON) && !d2d_mousein()) )
        return ((GetAsyncKeyState(kid) & 0x8000) == 0);

    return FALSE;
}

/* get mouse cursor pos */
extern void d2d_getmousexy (int *x, int *y)
{
    POINT p;

    GetCursorPos(&p);
    ScreenToClient(g_hwnd, &p);
    *x = p.x;
    *y = p.y;
    //*x = g_mousex;
    //*y = g_mousey;
}

static void start_loop ()
{
    double fpstime0, fpslasttime, lasttime;
    int loopn = 0;

    fpstime0 = fpslasttime = lasttime = d2d_clock ();

    while (1) {
        int msg;
        double now, begintime;
        double dtime;

        now = begintime = d2d_clock ();

        msg = peekmsg (NULL);
        if (msg == WM_QUIT)
            break;
        else if (msg != 0)
            continue;

        if(g_loopfunc && FALSE == g_loopfunc (now - lasttime))
            break;
        lasttime = now;
        loopn++;

        now = d2d_clock ();

        /* caculate fps once after awhile */
        if (now - fpslasttime >= 0.05) {
            g_fps = loopn/(now - fpstime0);
            fpslasttime = now;
        }
        if (loopn > 500) {
            fpstime0 = now;
            loopn = 0;
        }
        if (g_advicefps != 0) {
            dtime = now - begintime;

            if (dtime <= 1.0/g_advicefps)
                Sleep ((DWORD)(1000.0/g_advicefps - dtime*1000.0));
        }
    }
}

/* start single game loop mode */
extern void d2d_run (D2D_LOOPFUNC func)
{
    g_loopfunc = func;

    start_loop ();
}

extern double d2d_fps ()
{
    return g_fps;
}

/* create fullscreen mode
static int ddraw_init_f (HWND hwnd, int w, int h, int bpp)
{
    DDSURFACEDESC ddsd;
    DDSCAPS ddscap;
    HRESULT result;

    result = DirectDrawCreate (NULL, &g_ddraw, NULL);
    if (result != DD_OK)
        return FALSE;

    result = g_ddraw->SetCooperativeLevel (hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if (result != DD_OK) {
        g_ddraw->Release ();
        return FALSE;
    }

    result = g_ddraw->SetDisplayMode (w, h, bpp);
    if (result != DD_OK) {
        g_ddraw->Release ();
        return FALSE;
    }

    memset (&ddsd, 0, sizeof(ddsd));
    ddsd.dwSize = sizeof (ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;

    result = g_ddraw->CreateSurface (&ddsd, &g_primary, NULL);
    if (result != DD_OK) {
        g_ddraw->Release ();
        return FALSE;
    }

    ddscap.dwCaps = DDSCAPS_BACKBUFFER;
    result = g_primary->GetAttachedSurface (&ddscap, &g_back);
    if (result != DD_OK) {
        g_primary->Release ();
        g_ddraw->Release ();
        return FALSE;
    }

    return TRUE;
}
*/

/* create window mode */
static int ddraw_init_winmode (HWND hwnd)
{
    /* get canvas rect */
    RECT rc;
    int w, h;
    GetClientRect(hwnd, &rc);
    w = rc.right;
    h = rc.bottom;

    DDSURFACEDESC ddsd;
    HRESULT result;

    result = IDirectDraw_SetCooperativeLevel (g_ddraw, hwnd, DDSCL_NORMAL);
    if (result != DD_OK) {
        IDirectDraw_Release (g_ddraw);
        d2d_msgbox ("DDraw: SetCooperativeLevel fail");
        return FALSE;
    }

    /* create primary surface */
    memset (&ddsd, 0, sizeof(ddsd));
    ddsd.dwSize = sizeof (ddsd);
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    result = IDirectDraw_CreateSurface (g_ddraw, &ddsd, &g_primary, NULL);
    if (result != DD_OK) {
        IDirectDraw_Release (g_ddraw);
        d2d_msgbox ("DDraw: create primary surface fail");
        return FALSE;
    }

    result = IDirectDraw_CreateClipper (g_ddraw, 0, &g_clipper, NULL);
    if (result != DD_OK) {
        IDirectDrawSurface_Release (g_primary);
        IDirectDraw_Release (g_ddraw);
        d2d_msgbox ("DDraw: create primary clipper fail");
        return FALSE;
    }

    result = IDirectDrawClipper_SetHWnd (g_clipper, 0, hwnd);
    if (result != DD_OK) {
        IDirectDrawClipper_Release (g_clipper);
        IDirectDrawSurface_Release (g_primary);
        IDirectDraw_Release (g_ddraw);
        d2d_msgbox ("DDraw: primary clipper setHWnd fail");
        return FALSE;
    }

    result = IDirectDrawSurface_SetClipper (g_primary, g_clipper);
    if (result != DD_OK) {
        IDirectDrawClipper_Release (g_clipper);
        IDirectDrawSurface_Release (g_primary);
        IDirectDraw_Release (g_ddraw);
        d2d_msgbox ("DDraw: primary set clipper fail");
        return FALSE;
    }

    /* create back surface */
    memset (&ddsd, 0, sizeof(ddsd));
    ddsd.dwSize = sizeof (ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY/*DDSCAPS_SYSTEMMEMORY*/;
    ddsd.dwWidth = w;
    ddsd.dwHeight = h;

    result = IDirectDraw_CreateSurface (g_ddraw, &ddsd, &g_back, NULL);
    if (result != DD_OK) {
        IDirectDrawClipper_Release (g_clipper);
        IDirectDrawSurface_Release (g_primary);
        IDirectDraw_Release (g_ddraw);
        d2d_msgbox ("DDraw: create back surface fail");
        return FALSE;
    }

#if 1
    /* create back-surface clipper */
    IDirectDraw_CreateClipper (g_ddraw, 0, &g_backclipper, NULL);

    typedef	struct tagDDRGNDATA {
        RGNDATAHEADER	rdh;
        RECT		rect[8];
    } DDRGNDATA;

    DDRGNDATA	rgn;
    memset(&rgn, 0, sizeof(DDRGNDATA));
    rgn.rdh.dwSize = sizeof(RGNDATAHEADER);
    rgn.rdh.iType  = RDH_RECTANGLES;
    rgn.rdh.nCount = 1;
    rgn.rdh.nRgnSize = sizeof(RECT);
    rgn.rdh.rcBound.left   = 0;
    rgn.rdh.rcBound.top    = 0;
    rgn.rdh.rcBound.right  = ddsd.dwWidth;
    rgn.rdh.rcBound.bottom = ddsd.dwHeight;
    rgn.rect[0].left   = 0;
    rgn.rect[0].top    = 0;
    rgn.rect[0].right  = ddsd.dwWidth;
    rgn.rect[0].bottom = ddsd.dwHeight;

    IDirectDrawClipper_SetClipList (g_backclipper, (LPRGNDATA)&rgn, 0);

    result = IDirectDrawSurface_SetClipper (g_back, g_backclipper);
    if (result != DD_OK) {
        IDirectDrawClipper_Release (g_clipper);
        IDirectDrawSurface_Release (g_primary);
        IDirectDrawSurface_Release (g_back);
        IDirectDraw_Release (g_ddraw);
        d2d_msgbox ("DDraw: set back clipper fail");
        return FALSE;
    }

#endif
    return TRUE;
}


extern BOOL ddopen (HWND hwnd/*, int bpp, int iswin*/)
{
    BOOL result = FALSE;

    g_iswin = TRUE; /* iswin */
    g_hwnd = hwnd;

    g_ddrawdll = LoadLibraryW(L"ddraw.dll");
    if (!g_ddrawdll) {
        d2d_msgbox ("DDraw: Cant find ddraw.dll!");
        return FALSE;
    }

    T_DirectDrawCreate pcall = (T_DirectDrawCreate)GetProcAddress(g_ddrawdll, "DirectDrawCreate");
    if (!pcall) {
        d2d_msgbox ("ddraw.dll cant find DirectDrawCreate!");
        return FALSE;
    }
    result = pcall (NULL, &g_ddraw, NULL);
    if (result != DD_OK) {
        d2d_msgbox ("DDraw: DirectDrawCreate fail");
        return FALSE;
    }

    if (g_iswin == TRUE) {
        result = ddraw_init_winmode(hwnd);
    }
    else {
        /*result = ddraw_init_f (hwnd, w, h, bpp);*/
    }

    if (!result)
        exit(0);

    return result;
}

extern void ddclose ()
{
    if (g_clipper) IDirectDrawClipper_Release (g_clipper);
    if (g_primary) IDirectDrawSurface_Release (g_primary);
    if (g_back) IDirectDrawSurface_Release (g_back);
    if (g_ddraw) IDirectDraw_Release (g_ddraw);

    g_clipper = NULL;
    g_primary = NULL;
    g_back = NULL;
    g_ddraw = NULL;
}

extern HWND d2d_open(int w, int h)
{
    initdll_gdi32();
    initdll_winmm();

    HWND hwnd = winopen("D2D", w, h, NULL);
    BOOL r = ddopen(hwnd);
    if (r == FALSE) return NULL;

    return hwnd;
}

static IDirectDrawSurface *CreateOffScreenSurface (int w, int h)
{
    DDSURFACEDESC ddsd;
    IDirectDrawSurface *surface;

    memset (&ddsd, 0, sizeof(ddsd));
    ddsd.dwSize = sizeof (ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN|DDSCAPS_VIDEOMEMORY;
    ddsd.dwWidth = w;
    ddsd.dwHeight = h;

    HRESULT r = IDirectDraw_CreateSurface(g_ddraw, &ddsd, &surface, NULL);
    if (r != DD_OK)
        return NULL;
    else
        return surface;
}

static int set_colorkey (IDirectDrawSurface *surface, DWORD color)
{
    DDCOLORKEY ddck;

    ddck.dwColorSpaceLowValue = color;
    ddck.dwColorSpaceHighValue = color;

    return DD_OK == IDirectDrawSurface_SetColorKey (surface, DDCKEY_SRCBLT, &ddck);
}

D2DImage* d2d_newimage(char *path, DWORD colorkey)
{
    initdll_ole32();

    if (!path || !*path) return NULL;
    if (!g_hwnd) return NULL;

    BOOL isrc = FALSE;
    char* rctype = NULL;
    char* rcname = NULL;

    path = strdup(path); // remember to free
    rctype = strtok(path, ":");
    rcname = strtok(NULL, "");
    if (rcname && rcname[0]!='/' && rcname[0]!='\\')
        isrc = TRUE;

    /* load resource/file data to global mem */
    int rclen = 0;
    void* rcdata = NULL;

    HGLOBAL hGlobal = NULL;
    LPVOID pvData = NULL;

    if (isrc) {
        wchar_t* wrctype = UTF8toUnicode(rctype);
        wchar_t* wrcname = UTF8toUnicode(rcname);
        free(path);
        HRSRC hres = FindResourceW(NULL, wrcname, wrctype);
        if (!hres) {
            free(wrctype);
            free(wrcname);
            return NULL;
        }
        HGLOBAL hrc = LoadResource(NULL, hres);
        rclen = SizeofResource(NULL, hres);
        rcdata = LockResource(hrc);
        //printf ("SizeofResource: %d\n", rclen);
        hGlobal = GlobalAlloc(GMEM_MOVEABLE, rclen);
        pvData = GlobalLock( hGlobal );
        memcpy (pvData, rcdata, rclen);
        GlobalUnlock(hGlobal);
        free(rcdata);
    }
    else {
        wchar_t* wpath = UTF8toUnicode(path);
        free (path);
        HANDLE hFile=CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        free (wpath);
        if (!hFile) return NULL;

        DWORD dwFileSize=GetFileSize(hFile, NULL);
        if (dwFileSize <= 0) {
            CloseHandle(hFile);
            return NULL;
        }
        //printf ("filesize: %d\n", dwFileSize);
        hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
        pvData = GlobalLock( hGlobal );
        DWORD dwReadedSize;
        ReadFile(hFile, pvData, dwFileSize, &dwReadedSize, NULL);
        GlobalUnlock(hGlobal);
        CloseHandle(hFile);
    }

    /* load IPicture */
    IStream *pIStream = NULL;
    IPicture *pIPicture = NULL;
    int w, h;
    HRESULT r;

    // we DIY IID_IPicture
    GUID ipicture;
    ipicture.Data1 = 0x7BF80980;
    ipicture.Data2 = 0xBF32;
    ipicture.Data3 = 0x101A;
    ipicture.Data4[0] = 0x8B;
    ipicture.Data4[1] = 0xBB;
    ipicture.Data4[2] = 0x0;
    ipicture.Data4[3] = 0xAA;
    ipicture.Data4[4] = 0x0;
    ipicture.Data4[5] = 0x30;
    ipicture.Data4[6] = 0xC;
    ipicture.Data4[7] = 0xAB;

    r = g_CreateStreamOnHGlobal(hGlobal, TRUE, &pIStream);
    //printf ("CreateStreamOnHGlobal: %d\n", r);
    r = g_OleLoadPicture(pIStream, 0, FALSE, &ipicture, (LPVOID*)&pIPicture);
    //printf ("OleLoadPicture: r:%x, iPicture:%x\n", r, pIPicture);
    if (r != S_OK) {
        pIStream->lpVtbl->Release(pIStream);
        GlobalFree(hGlobal);
        return NULL;
    }
    pIStream->lpVtbl->Release(pIStream);
    GlobalFree(hGlobal);

    OLE_XSIZE_HIMETRIC hmWidth = 0;
    OLE_YSIZE_HIMETRIC hmHeight = 0;
    pIPicture->lpVtbl->get_Width(pIPicture, &hmWidth);
    //printf ("hmWidth: %d\n", hmWidth);
    pIPicture->lpVtbl->get_Height(pIPicture, &hmHeight);
    //printf ("hmHeight: %d\n", hmHeight);

    /* himetric -> inch -> pixcel */
    HDC windc = GetDC(0);
    w = (int)(1.0 * hmWidth / 2540 * g_GetDeviceCaps(windc, LOGPIXELSX) + 0.5);
    h = (int)(1.0 * hmHeight / 2540 * g_GetDeviceCaps(windc, LOGPIXELSY) + 0.5);
    ReleaseDC(0, windc);
    //printf ("w: %d, h: %d\n", w, h);

    /* create a surface, get hdc */
    IDirectDrawSurface* surface = NULL;
    HDC surhdc = NULL;
    surface = CreateOffScreenSurface (w, h);
    if (!surface) {
        pIPicture->lpVtbl->Release(pIPicture);
        return NULL;
    }

    IDirectDrawSurface_GetDC (surface, &surhdc);
    pIPicture->lpVtbl->Render(pIPicture, surhdc,0,0,w,h,0,hmHeight,hmWidth,-hmHeight,NULL);
    IDirectDrawSurface_ReleaseDC (surface, surhdc);
    pIPicture->lpVtbl->Release(pIPicture);

    set_colorkey(surface, colorkey);

    /* craete D2DImage */
    D2DImage* img = (D2DImage *)malloc (sizeof(D2DImage));

    img->w = w;
    img->h = h;
    img->surface = surface;
    img->colorkey = colorkey;

    return img;
}

extern void d2d_freeimage (D2DImage *img)
{
    IDirectDrawSurface *surface;

    if (img == NULL)
        return;

    surface = (IDirectDrawSurface *)img->surface;
    IDirectDrawSurface_Release (surface);

    free (img);
}

/* D2DImage blit zoom and part */
extern void d2d_bltz (D2DImage *img, float x, float y, RECT *src, float zoom)
{
    RECT dest, _src;
    DDBLTFX bltfx;

    if (img == NULL)
        return;

    bltfx.dwSize = sizeof (bltfx);
    bltfx.ddckSrcColorkey.dwColorSpaceLowValue = img->colorkey;
    bltfx.ddckSrcColorkey.dwColorSpaceHighValue = img->colorkey;

    if (src == NULL) {
        SetRect (&dest, (int)x, (int)y, (int)(x+img->w*zoom), (int)(y+img->h*zoom));
        SetRect (&_src, 0, 0, img->w, img->h);
    }
    else {
        SetRect (&dest, (int)x, (int)y, (int)(x+(src->right-src->left)*zoom), (int)(y+(src->bottom-src->top)*zoom));
        CopyRect(&_src, src);
    }

    IDirectDrawSurface_Blt (g_back, &dest, (IDirectDrawSurface *)img->surface, &_src, DDBLT_KEYSRC | DDBLT_WAIT, &bltfx);
}

/* D2DImage blit part*/
extern void d2d_blt (D2DImage *img, float x, float y)
{
    if (img) d2d_bltz (img, x, y, NULL, 1);
}

/* D2DImage blit part*/
extern void d2d_bltp (D2DImage *img, float x, float y, RECT *src)
{
    RECT dest;
    DDBLTFX bltfx;

    if (img == NULL)
        return;

    bltfx.dwSize = sizeof (bltfx);
    bltfx.ddckSrcColorkey.dwColorSpaceLowValue = img->colorkey;
    bltfx.ddckSrcColorkey.dwColorSpaceHighValue = img->colorkey;

    int srcw = src->right - src->left;
    int srch = src->bottom - src->top;
    SetRect (&dest, (int)x, (int)y, (int)(x+srcw), (int)(y+srch));
    IDirectDrawSurface_Blt (g_back, &dest, (IDirectDrawSurface *)img->surface, src, DDBLT_KEYSRC | DDBLT_WAIT, &bltfx);
}

/* D2DImage blit extend
extern void d2d_bltx (D2DImage *img, RECT *src, RECT *dest)
{
    DDBLTFX bltfx;

    if (img == NULL)
        return;

    bltfx.dwSize = sizeof (bltfx);
    bltfx.ddckSrcColorkey.dwColorSpaceLowValue = img->colorkey;
    bltfx.ddckSrcColorkey.dwColorSpaceHighValue = img->colorkey;

    g_back->Blt (dest, (IDirectDrawSurface *)img->surface, src, DDBLT_KEYSRC | DDBLT_WAIT, &bltfx);
}
*/

/* clear back surface */
extern void d2d_clrscr (DWORD color)
{
    DDBLTFX ddbltfx;
    ddbltfx.dwSize = sizeof(ddbltfx);
    ddbltfx.dwFillColor = color;

    if (g_back)
        IDirectDrawSurface_Blt(g_back, NULL, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &ddbltfx);
}

extern void d2d_flip ()
{
    RECT rc;
    GetClientRect( g_hwnd, &rc);
    ClientToScreen( g_hwnd, (POINT*)&rc.left );
    ClientToScreen( g_hwnd, (POINT*)&rc.right );
    RECT rcS;
    GetClientRect(g_hwnd, &rcS);

    if (!g_primary) return;

    if (g_iswin) {
        IDirectDrawSurface_Blt (g_primary, &rc, g_back, &rcS, DDBLT_WAIT, NULL);
    }
    else {
        IDirectDrawSurface_Flip (g_primary, NULL, DDFLIP_WAIT);
    }
}

extern HFONT d2d_newfont (int size, const char* weight, const char *facename)
{
    HFONT font;
    wchar_t* wfacename;
    int wt = FW_NORMAL;

    if (strcasecmp(weight, "thin") == 0)
        wt = FW_THIN;
    else if (strcasecmp(weight, "bold") == 0)
        wt = FW_BOLD;
    else if (strcasecmp(weight, "bolder") == 0)
        wt = FW_HEAVY;
    else
        wt = FW_NORMAL;
    wfacename = UTF8toUnicode(facename);
    font = g_CreateFontW(size, 0, 0, 0,wt,FALSE,FALSE,FALSE,
    DEFAULT_CHARSET, OUT_STROKE_PRECIS,
    CLIP_STROKE_PRECIS, PROOF_QUALITY,
    VARIABLE_PITCH |FF_ROMAN,wfacename);

    free(wfacename);
    return font;
}

extern void d2d_freefont (HFONT font)
{
    g_DeleteObject (font);
}

static void ftprint (HFONT font, int x, int y, DWORD color, const char *text)
{
    HDC hdc;

    if (!text) return;

    // conversion from RGB to BGR
    unsigned char* lc = (unsigned char*)&color;
    DWORD fcolor = color;
    unsigned char* fl = (unsigned char*)&fcolor;
    fl[0] = lc[2];
    fl[2] = lc[0];

    if(g_back && SUCCEEDED(IDirectDrawSurface_GetDC(g_back, &hdc))) {

        wchar_t* wtext = UTF8toUnicode(text);
        g_SetBkMode(hdc, TRANSPARENT);

        if (fcolor != 0)
            g_SetTextColor (hdc, fcolor);
        g_SelectObject (hdc, font);

        g_TextOutW (hdc, x, y, wtext, lstrlenW(wtext));

        IDirectDrawSurface_ReleaseDC (g_back, hdc);
        free(wtext);
    }

}

extern int d2d_print (HFONT font, const char *format, ...)
{
    static char txtbuf[1024];

    va_list ap;

    va_start (ap, format);

    vsprintf (txtbuf, format, ap);

    va_end(ap);

    ftprint (font, g_x, g_y, g_textcolor, txtbuf);
    return 0;
}

extern void d2d_setfontcolor(DWORD color)
{
    g_textcolor = color;
}

extern void d2d_gotoxy(int x, int y)
{
    g_x = x;
    g_y = y;
}




/* wav sound stream */
static int init_sound()
{
    static BOOL loaded = FALSE;

    if (loaded == TRUE) return 0;
    loaded = TRUE;

    initdll_winmm();

    /* same as system default volume */
    DWORD volume;
    g_waveOutGetVolume(0, &volume);
    g_waveOutSetVolume(0, volume);

    return 1;
}

static void playsound(void *p)
{
    char* filename;
    BOOL isrc = FALSE;

    if (!p) return;

    char* path = strdup(p);
    char* rctype = strtok(path, ":");
    char* rcname = strtok(NULL, "");

    filename = (char*)p;
    if (rctype && rcname) {
        isrc = TRUE;
        filename = rcname;
    }
    else if (filename[0] == ':') {
        isrc = TRUE;
        filename = filename + 1;
    }
    free(path);

    wchar_t* wfilename = UTF8toUnicode(filename);

    if (isrc)
        g_PlaySoundW(wfilename, NULL, SND_RESOURCE|SND_ASYNC);
    else
        g_PlaySoundW(wfilename, NULL, SND_FILENAME|SND_ASYNC);

    free(wfilename);
}

extern void d2d_playsound (const char* filename)
{
    init_sound();

    _beginthread(playsound, 0, (void*)filename);
}

extern void d2d_stopsound()
{
    init_sound();

    g_sndPlaySoundA (NULL, 0);
}


/* midi */

#include "native_midi.h"

extern HANDLE d2d_midi_loadsong(const char *midifile)
{
    NativeMidiSong* song = native_midi_loadsong(midifile);
    return (HANDLE)song;
}

extern void d2d_midi_freesong(HANDLE song)
{
    native_midi_freesong((NativeMidiSong*)song);
}

extern void d2d_midi_playsong(HANDLE song)
{
    native_midi_start( (NativeMidiSong*)song );
}

extern void d2d_midi_stop()
{
    native_midi_stop();
}

extern BOOL d2d_midi_playing()
{
    return native_midi_active();
}

/* 0-128 */
extern void d2d_midi_setvolume(int volume)
{
    native_midi_setvolume(volume);
}


/* variable alias
*/
#define VARPOOLSIZE 128
struct vartype {
    void* var;
    char* name;
    struct vartype* next;
} *g_varpool[VARPOOLSIZE];

static unsigned strhash (const char *s)
{
    register unsigned code = 5381;

    if (s == NULL) return 0;
    while (*s)
        code += (code << 5) + (*s++);

    return code;
}

static void init_varpool()
{
    static int did = 0;
    if (did) return;
    memset(g_varpool, 0, sizeof(g_varpool));
    did = 1;
}

extern void d2d_setvar(const char* name, void* var)
{
        unsigned hash;
        struct vartype **hn;

        if (!name) return;
        init_varpool ();

        hash = strhash(name) % VARPOOLSIZE;
        hn = &g_varpool[hash];
        while (*hn) {
                struct vartype *p = *hn;
                if (strcmp(p->name, name) == 0) {
                        p->var = var;
                        return;
                }
                hn = &p->next;
        }
        *hn = (struct vartype *)malloc (sizeof(struct vartype));
        (*hn)->var = var;
        (*hn)->name = strdup(name);
        (*hn)->next = NULL;
}

extern void* d2d_getvar(const char* name)
{
        unsigned hash;
        struct vartype *p;

        if (!name) return NULL;
        init_varpool ();

        hash = strhash ((char *)name) % VARPOOLSIZE;
        p = g_varpool[hash];
        while (p) {
                if (strcmp(p->name, name) == 0)
                        return p->var;
                p = p->next;
        }
        return NULL;
}
